Skip to content

《C++面向对象程序设计》期末试卷B (精选02)

一、判断题(对的打"√",错的打"×")(每题 1 分,共计 10 分)

  1. 编译 C++ 源程序时,出现了警告错(Warning)也可以生成可执行文件。
查看答案与解析

答案:√

解析: C++ 编译器的诊断信息分两类:

  • Error(错误):严重违反语法规则,编译器无法生成目标代码
  • Warning(警告):潜在问题但不影响代码生成,编译器仍可生成可执行文件

常见警告:未使用的变量、隐式类型转换、有符号/无符号比较等。


难度: ⭐ 考点: #编译过程 #Warning与Error

💡 学习锦囊

📖 相关公式与知识点:

  • Error → 编译失败,无目标文件
  • Warning → 编译成功,可生成可执行文件
  • 编译选项:-Wall 开启警告,-Werror 将警告视为错误

易错点

  • 误以为 Warning 也会阻止编译
  • 实际项目应尽量消除所有警告
🔄 举一反三
  1. 下列说法正确的是( )
    • A. 有 Warning 就不能生成 .obj 文件
    • B. 有 Error 就不能生成可执行文件
    • C. Warning 和 Error 都不影响编译
    • D. Warning 比 Error 更严重
    查看练习答案与解析

    答案:B 解析:Error 是致命错误,阻止可执行文件生成。Warning 不影响编译和生成。

  1. 标识符规定大小写字母没有区别。
查看答案与解析

答案:×

解析: C++ 是严格区分大小写的语言。valueValueVALUE 是三个不同的标识符。关键字全部为小写:intclassreturn


难度: ⭐ 考点: #标识符 #大小写敏感

💡 学习锦囊

📖 相关公式与知识点:

  • 标识符规则:字母、数字、下划线组成,不能以数字开头
  • C++ 区分大小写:abcAbcABC

易错点

  • 大小写混淆是初学者最常见的编译错误来源
🔄 举一反三
  1. 下列标识符中,与 myFunc 表示同一变量的是( )
    • A. MyFunc
    • B. myfunc
    • C. MYFUNC
    • D. 以上都不是
    查看练习答案与解析

    答案:D 解析:C++ 严格区分大小写,四个标识符各不相同。

  1. 数组中所有元素的类型都是相同的。
查看答案与解析

答案:√

解析: 数组是同一类型数据元素的有序集合。定义数组时必须指定元素类型,所有元素都必须是该类型。需要存储不同类型数据应使用结构体或类。


难度: ⭐ 考点: #数组 #数据类型一致性

💡 学习锦囊

📖 相关公式与知识点:

  • 数组:类型名 数组名[长度],元素类型相同、内存连续、下标从 0 开始
  • 对比结构体:成员可以是不同类型

思路分析

数组的核心特征是"同类型元素的有序集合",这是数组与结构体的本质区别。

🔄 举一反三
  1. 若需存储学生的姓名、年龄和成绩,应使用( )
    • A. 数组
    • B. 结构体
    • C. 指针
    • D. 引用
    查看练习答案与解析

    答案:B 解析:不同类型数据应使用结构体。数组要求元素类型相同。

  1. 创建引用时要用一个同类型的变量进行初始化。
查看答案与解析

答案:√

解析: 引用是已存在变量的别名,创建时必须满足:①必须初始化 ②类型必须匹配。

cpp
int a = 10;
int &ref = a;       // ✅ 同类型初始化
double &ref2 = a;   // ❌ 类型不匹配
int &ref3;          // ❌ 必须初始化

例外:const 引用可绑定到不同类型(创建临时变量)。


难度: ⭐ 考点: #引用 #引用初始化 #类型匹配

💡 学习锦囊

📖 相关公式与知识点:

  • 引用声明:类型 &引用名 = 变量名
  • 引用特性:必须初始化、不可重绑定、与原变量共享内存

易错点

  • 非 const 引用不能绑定到右值
  • 引用不能像指针那样先声明后赋值
🔄 举一反三
  1. 下列引用声明中,正确的是( )
    • A. int &ref;
    • B. int &ref = 10;
    • C. int a; int &ref = a;
    • D. int a; double &ref = a;
    查看练习答案与解析

    答案:C 解析:A 缺少初始化,B 绑定到右值,C 同类型初始化正确,D 类型不匹配。

  1. 静态数据成员必须在构造函数的成员初始化列表中进行初始化。
查看答案与解析

答案:×

解析: 静态数据成员不能在构造函数初始化列表中初始化。静态成员属于类而非对象,必须在类体外单独初始化。

cpp
class MyClass {
    static int count;   // 类内声明
};
int MyClass::count = 0; // 类体外初始化

特例:static const int 可在类内初始化。


难度: ⭐⭐ 考点: #静态数据成员 #初始化方式

💡 学习锦囊

📖 相关公式与知识点:

  • 静态成员初始化:类型 类名::静态成员名 = 值;(类体外)
  • 特例:static const int 可类内初始化
  • 静态成员属于类,所有对象共享一份

易错点

  • 静态成员不能出现在构造函数初始化列表中
  • 静态成员不计入 sizeof 对象大小
🔄 举一反三
  1. 下列静态成员初始化方式,正确的是( )
    • A. 在构造函数体内赋值
    • B. 在构造函数初始化列表中初始化
    • C. 在类体外用 类名::成员名 初始化
    • D. 不需要初始化
    查看练习答案与解析

    答案:C 解析:静态成员必须在类体外用 类型 类名::成员名 = 值 初始化。

  1. 常数据成员在常成员函数中的值是不允许改变的,而在非常成员函数中是允许改变的。
查看答案与解析

答案:×

解析: 常数据成员无论在常成员函数还是非常成员函数中,其值都不允许改变。const 修饰的数据成员一旦初始化就不可修改,这是 const 的基本语义保证。

cpp
class MyClass {
    const int value;
public:
    MyClass(int v) : value(v) {}
    void normalFunc() { /* value = 10; ❌ */ }
    void constFunc() const { /* value = 10; ❌ */ }
};

难度: ⭐⭐ 考点: #常数据成员 #const语义

💡 学习锦囊

📖 相关公式与知识点:

  • 常数据成员:const 类型 成员名,必须在初始化列表中初始化
  • 常数据成员的"常"是绝对的,任何函数都不能修改
  • 常成员函数不能修改任何非 mutable 数据成员

易错点

  • 常数据成员的不可修改性与函数是否为 const 无关
  • 常成员函数不能修改非 mutable 数据成员(不仅是常数据成员)
🔄 举一反三
  1. 下列说法正确的是( )
    • A. 常成员函数只能访问常数据成员
    • B. 非常成员函数可以修改常数据成员
    • C. 常数据成员在任何成员函数中都不可修改
    • D. 常成员函数可以修改非常数据成员
    查看练习答案与解析

    答案:C 解析:常数据成员在任何函数中都不可修改(C 对)。常成员函数可访问所有成员(A 错),不能修改非 mutable 数据成员(D 错)。

  1. 成员函数与一般函数一样可以重载和设置参数的默认值,但不能设置为内联函数。
查看答案与解析

答案:×

解析: 成员函数可以设置为内联函数。方式有两种:

  1. 隐式内联:类体内定义的成员函数自动成为内联函数
  2. 显式内联:使用 inline 关键字声明
cpp
class MyClass {
public:
    void func1() { /* ... */ }           // 隐式内联
    inline void func2();                 // 显式声明
};
inline void MyClass::func2() { /* ... */ }

难度: ⭐ 考点: #内联函数 #成员函数 #inline

💡 学习锦囊

📖 相关公式与知识点:

  • 内联函数:编译时在调用处展开,减少函数调用开销
  • 类体内定义 → 自动内联
  • inline 只是建议,编译器可忽略

易错点

  • 成员函数完全可以是内联函数
  • 递归函数、含复杂逻辑的函数不适合内联
🔄 举一反三
  1. 下列关于内联函数的说法,错误的是( )
    • A. 类体内定义的成员函数默认是内联的
    • B. 内联函数一定会被编译器展开
    • C. inline 关键字可以声明内联函数
    • D. 内联函数适用于短小频繁调用的函数
    查看练习答案与解析

    答案:B 解析inline 只是建议,编译器可能不展开(如函数体过大、含递归等)。

  1. 定义对象指针时也要调用构造函数。
查看答案与解析

答案:×

解析: 定义对象指针时不会调用构造函数。指针本身只是一个存储地址的变量,定义指针并不创建对象。

cpp
MyClass *p;              // 不调用构造函数
MyClass obj;             // 调用构造函数
MyClass *p2 = new MyClass; // new 创建对象时调用构造函数

难度: ⭐⭐ 考点: #对象指针 #构造函数调用时机

💡 学习锦囊

📖 相关公式与知识点:

  • MyClass *p → 不调用构造函数
  • MyClass obj → 调用构造函数
  • new MyClass → 调用构造函数

易错点

  • 定义指针 ≠ 创建对象
  • MyClass *p = new MyClass 中是 new MyClass 调用构造函数
🔄 举一反三
  1. 下列哪种情况不会调用构造函数?( )
    • A. MyClass obj;
    • B. MyClass *p = new MyClass;
    • C. MyClass *p;
    • D. MyClass arr[3];
    查看练习答案与解析

    答案:C 解析:C 仅定义指针不调用构造函数。A、B、D 都会创建对象调用构造函数。

  1. 派生类只继承基类中的公有成员和保护成员,而不继承私有成员。
查看答案与解析

答案:×

解析: 派生类继承基类的所有成员(包括公有、保护和私有成员),只是私有成员在派生类中不可直接访问

cpp
class Derived : public Base {
    void func() {
        pub = 1;     // ✅ 可访问
        prot = 2;    // ✅ 可访问
        // priv = 3; // ❌ 不可直接访问,但确实被继承了
    }
};

私有成员虽被继承(存在于派生类对象中,占内存),但只能通过基类的公有/保护成员函数间接访问。


难度: ⭐⭐ 考点: #继承 #访问控制 #私有成员继承

💡 学习锦囊

📖 相关公式与知识点:

基类成员public 继承protected 继承private 继承
publicpublicprotectedprivate
protectedprotectedprotectedprivate
private不可直接访问不可直接访问不可直接访问
  • 关键区别:继承可访问

易错点

  • "不继承私有成员"是常见误解,实际是"继承但不可直接访问"
  • 私有成员仍存在于派生类对象中,占内存
🔄 举一反三
  1. 关于继承,下列说法正确的是( )
    • A. 派生类不继承基类私有成员,不占内存
    • B. 派生类继承所有成员,但私有成员不可直接访问
    • C. 保护继承时,基类公有成员仍为公有
    • D. 私有继承时,基类保护成员变为公有
    查看练习答案与解析

    答案:B 解析:派生类继承所有成员包括私有成员(A 错、B 对)。保护继承时公有成员变为保护(C 错)。私有继承时保护成员变为私有(D 错)。

  1. 静态联编和动态联编都是在编译时进行的,二者的区别仅是前者对非虚函数,后者对虚函数。
查看答案与解析

答案:×

解析: 静态联编和动态联编的根本区别在于联编时机不同:

  • 静态联编(早绑定):在编译时确定函数调用
  • 动态联编(晚绑定):在运行时确定函数调用,通过虚函数表实现

动态联编不是在编译时进行的,而是在运行时根据对象的实际类型确定调用哪个函数。


难度: ⭐⭐ 考点: #静态联编 #动态联编 #多态

💡 学习锦囊

📖 相关公式与知识点:

  • 静态联编:编译时绑定,根据指针/引用的静态类型确定
  • 动态联编:运行时绑定,根据对象的实际类型确定
  • 动态联编条件:虚函数 + 基类指针/引用调用

易错点

  • 动态联编在运行时进行,不是编译时
  • 用对象直接调用虚函数是静态联编
🔄 举一反三
  1. 下列关于联编的说法,正确的是( )
    • A. 所有虚函数调用都是动态联编
    • B. 动态联编在运行时进行,静态联编在编译时进行
    • C. 非虚函数也可以实现动态联编
    • D. 联编时机对程序行为没有影响
    查看练习答案与解析

    答案:B 解析:用对象直接调用虚函数是静态联编(A 错)。动态联编在运行时、静态联编在编译时(B 对)。非虚函数只能静态联编(C 错)。

二、单项选择题(每题 2 分,总计 20 分)

  1. 下列关于类的描述中,错误的是( )。
    • A. 类就是 C 语言中的结构类型
    • B. 类是创建对象的模板
    • C. 类是抽象数据类型的实现
    • D. 类是具有共同行为的若干对象的统一描述体
查看答案与解析

答案:A

解析: 类不仅仅是 C 语言中的结构类型。类具有结构体所没有的核心特性:封装性(访问控制)、成员函数、继承与多态。虽然 C++ 中 struct 和 class 功能几乎相同,但从概念上讲,"类就是结构类型"忽略了面向对象的本质特征。


难度: ⭐ 考点: #类与结构体 #封装 #面向对象

💡 学习锦囊

📖 相关公式与知识点:

  • class 默认 private,struct 默认 public
  • 类的三大特性:封装、继承、多态

易错点

  • C++ 中 struct 也可有成员函数和访问控制
  • "类就是结构类型"忽略了 OOP 特征
🔄 举一反三
  1. 关于 class 和 struct,下列说法正确的是( )
    • A. struct 不能有成员函数
    • B. class 不能有数据成员
    • C. class 默认 private,struct 默认 public
    • D. 两者完全不能互换
    查看练习答案与解析

    答案:C 解析:C++ 中 struct 和 class 唯一区别是默认访问权限。

  1. 下列关于构造函数的描述中,错误的是( )。
    • A. 构造函数可以重载
    • B. 构造函数名同类名
    • C. 带参数的构造函数具有类型转换作用
    • D. 构造函数是系统自动调用的
查看答案与解析

答案:C

解析: "带参数的构造函数具有类型转换作用"说法过于宽泛。只有单参数构造函数才具有隐式类型转换作用,多参数构造函数不具有此功能。可用 explicit 禁止隐式转换。


难度: ⭐⭐ 考点: #构造函数 #类型转换 #单参数构造函数

💡 学习锦囊

📖 相关公式与知识点:

  • 单参数构造函数 → 可隐式类型转换
  • explicit 关键字 → 禁止隐式转换

易错点

  • 不是所有带参数的构造函数都有类型转换作用
  • 建议对单参数构造函数加 explicit
🔄 举一反三
  1. 下列哪个声明可以防止隐式类型转换?( )
    • A. MyClass(int n);
    • B. explicit MyClass(int n);
    • C. MyClass(int a, int b);
    • D. MyClass();
    查看练习答案与解析

    答案:B 解析explicit 禁止隐式类型转换。

  1. 下列关于析构函数的描述中,错误的是( )。
    • A. 析构函数的函数体都为空
    • B. 析构函数是用来释放对象的
    • C. 析构函数是系统自动调用的
    • D. 析构函数是不能重载的
查看答案与解析

答案:A

解析: 析构函数的函数体不一定为空。析构函数常用于执行清理工作,如释放动态内存、关闭文件等。

cpp
~MyClass() {
    delete[] data;   // 函数体不为空
}

难度: ⭐ 考点: #析构函数 #资源释放

💡 学习锦囊

📖 相关公式与知识点:

  • 析构函数:~类名(),无参数、无返回值、不可重载
  • 析构顺序:与构造顺序相反
  • RAII 原则:资源获取即初始化,析构时释放

易错点

  • 有动态分配资源时必须自定义析构函数
  • 忘记释放动态内存导致内存泄漏
🔄 举一反三
  1. 下列情况中,必须自定义析构函数的是( )
    • A. 类中只有 int 类型成员
    • B. 类中使用 new 分配了内存
    • C. 类中没有数据成员
    • D. 类中只有静态成员
    查看练习答案与解析

    答案:B 解析:使用 new 分配内存必须在析构函数中用 delete 释放。

  1. 下列关于静态成员的描述中,错误的是( )。
    • A. 静态成员都是使用 static 来说明的
    • B. 静态成员是属于类的,不是属于某个对象的
    • C. 静态成员只可以用类名加作用域运算符来引用,不可用对象引用
    • D. 静态数据成员的初始化是在类体外进行的
查看答案与解析

答案:C

解析: 静态成员既可以用类名引用,也可以用对象引用

cpp
MyClass::count = 1;  // ✅ 用类名引用(推荐)
obj.count = 2;        // ✅ 用对象引用(也可以)

推荐使用类名引用,因为静态成员属于类而非对象。


难度: ⭐⭐ 考点: #静态成员 #访问方式

💡 学习锦囊

📖 相关公式与知识点:

  • 静态成员访问:类名::静态成员对象.静态成员
  • 静态成员函数:没有 this 指针,只能访问静态成员

易错点

  • 推荐用类名引用静态成员
  • 静态成员函数不能声明为 const
🔄 举一反三
  1. 下列关于静态成员函数的描述,正确的是( )
    • A. 静态成员函数有 this 指针
    • B. 静态成员函数只能访问静态成员
    • C. 静态成员函数不能通过类名调用
    • D. 静态成员函数可以声明为 const
    查看练习答案与解析

    答案:B 解析:静态成员函数没有 this 指针(A 错),只能访问静态成员(B 对),可通过类名调用(C 错),不能声明为 const(D 错)。

  1. 下列关于友元函数的描述中,错误的是( )。
    • A. 友元函数不是成员函数
    • B. 友元函数只可访问类的私有成员
    • C. 友元函数的调用方法同一般函数
    • D. 友元函数可以是另一类中的成员函数
查看答案与解析

答案:B

解析: 友元函数可以访问类的所有成员(包括公有、保护和私有成员),而不是"只可访问私有成员"。


难度: ⭐⭐ 考点: #友元函数 #访问权限

💡 学习锦囊

📖 相关公式与知识点:

  • 友元函数可访问所有成员(public、protected、private)
  • 友元关系是单向的、不可传递的

易错点

  • 友元函数可访问所有成员,不仅仅是私有成员
  • 友元关系不是相互的,也不能继承
🔄 举一反三
  1. 若类 A 声明类 B 为友元,则下列说法正确的是( )
    • A. 类 A 的成员函数可访问类 B 的私有成员
    • B. 类 B 的成员函数可访问类 A 的私有成员
    • C. A 和 B 互相可访问对方私有成员
    • D. 类 B 的子类也可访问类 A 的私有成员
    查看练习答案与解析

    答案:B 解析:友元关系是单向的,A 声明 B 为友元意味着 B 可访问 A 的私有成员。友元关系不能继承。

  1. void Set(A &a); 是类 A 中一个成员函数的说明,其中 A &a 的含义是( )。
    • A. 类 A 的对象引用 a 作该函数的参数
    • B. 类 A 的对象 a 的地址值作函数的参数
    • C. 表达式变量 A 与变量 a 按位与作函数参数
    • D. 指向类 A 对象指针 a 作函数参数
查看答案与解析

答案:A

解析:A &a 表示声明了一个类 A 的引用参数 a。在参数声明中,& 表示引用,不是取地址运算符,也不是按位与运算符。

区分 & 的三种含义:①声明中→引用 ②表达式中→取地址 ③位运算中→按位与


难度: ⭐ 考点: #引用参数 #&运算符含义

💡 学习锦囊

📖 相关公式与知识点:

  • 引用参数:类型 &参数名,传递对象的别名
  • 指针参数:类型 *参数名,传递对象的地址

思路分析

在函数参数声明中看到 &,首先想到"引用"。

🔄 举一反三
  1. 下列函数参数声明中,属于引用传递的是( )
    • A. void func(A a)
    • B. void func(A *a)
    • C. void func(A &a)
    • D. void func(A **a)
    查看练习答案与解析

    答案:C 解析:A 值传递,B 指针传递,C 引用传递,D 指向指针的指针传递。

  1. 下列关于运算符 new 的描述中,错误的是( )。
    • A. 它可以创建对象或变量
    • B. 它可以创建对象数组或一般类型数组
    • C. 用它创建对象或对象数组时要调用相应的构造函数
    • D. 用它创建的对象可以不用 delete 运算符释放
查看答案与解析

答案:D

解析:new 创建的对象必须delete 释放,否则导致内存泄漏new 在堆上分配内存,不会自动释放。


难度: ⭐ 考点: #new运算符 #内存管理 #delete

💡 学习锦囊

📖 相关公式与知识点:

  • new / delete 必须配对
  • new[] / delete[] 必须配对
  • 建议使用智能指针代替裸指针

易错点

  • delete 释放 new[] 分配的内存是未定义行为
  • delete 后指针不会自动置空(悬空指针)
🔄 举一反三
  1. 下列关于 new/delete 的描述,正确的是( )
    • A. delete 可释放 new[] 分配的内存
    • B. new 分配的内存会自动释放
    • C. new[] 必须与 delete[] 配对使用
    • D. delete 后指针自动变为 nullptr
    查看练习答案与解析

    答案:C 解析new[]delete[] 必须配对。A、B、D 均错误。

  1. 派生类的对象可以直接访问的基类成员是( )。
    • A. 公有继承的公有成员
    • B. 保护继承的公有成员
    • C. 私有继承的公有成员
    • D. 公有继承的保护成员
查看答案与解析

答案:A

解析: 派生类对象(外部代码)可直接访问的基类成员,必须是经过继承后仍为 public 的成员。

  • A:✅ 公有继承下,基类公有成员仍为公有
  • B:❌ 保护继承下,基类公有成员变为保护
  • C:❌ 私有继承下,基类公有成员变为私有
  • D:❌ 保护成员外部不可访问

难度: ⭐⭐ 考点: #继承方式 #访问权限

💡 学习锦囊

📖 相关公式与知识点:

  • 只有 public 继承的 public 成员才能被外部对象直接访问
  • 保护成员和私有成员在任何继承方式下都不能被外部对象直接访问

易错点

  • 保护成员即使在公有继承下也不能被外部对象直接访问
🔄 举一反三
  1. 若有 class D : private B,则 D 的成员函数可访问 B 中的( )
    • A. 公有成员
    • B. 保护成员
    • C. 公有和保护成员
    • D. 公有、保护和私有成员
    查看练习答案与解析

    答案:C 解析:私有继承下,D 的成员函数可访问 B 的公有和保护成员,但不能访问 B 的私有成员。

  1. 下列运算符中,不可以重载的是( )。
    • A. &&
    • B. &
    • C. [ ]
    • D. ::
查看答案与解析

答案:D

解析: C++ 中不可重载的运算符:. .* :: ?: sizeof typeid

::(作用域解析运算符)不可重载,&&&[] 都可以重载。


难度: ⭐ 考点: #运算符重载 #不可重载运算符

💡 学习锦囊

📖 相关公式与知识点:

  • 不可重载运算符:. .* :: ?: sizeof typeid
  • 运算符重载不能改变优先级、结合性和操作数个数

易错点

  • .:: 是最常考的不可重载运算符
🔄 举一反三
  1. 下列运算符中,不可以重载的是( )
    • A. ->
    • B. new
    • C. .
    • D. ()
    查看练习答案与解析

    答案:C 解析:成员访问运算符 . 不可重载。->new() 都可重载。

  1. 下列的成员函数中,纯虚函数是( )。
  • A. virtual void f1() = 0
  • B. void f1() = 0
  • C. virtual void f1() {}
  • D. virtual void f1() == 0
查看答案与解析

答案:A

解析: 纯虚函数的标准格式:virtual 返回类型 函数名(参数列表) = 0;

  • A:✅ 标准纯虚函数
  • B:❌ 缺少 virtual
  • C:❌ 有函数体,是普通虚函数
  • D:❌ == 0 是比较运算,语法错误

难度: ⭐ 考点: #纯虚函数 #抽象类 #virtual

💡 学习锦囊

📖 相关公式与知识点:

  • 纯虚函数:virtual 返回类型 函数名(参数) = 0;
  • 含纯虚函数的类是抽象类,不能实例化
  • = 0 是纯虚函数标志,不是赋值

易错点

  • 纯虚函数必须同时有 virtual= 0
  • 抽象类不能创建对象,但可定义指针和引用
🔄 举一反三
  1. 下列关于纯虚函数和抽象类的描述,正确的是( )
    • A. 抽象类可以创建对象
    • B. 含有虚函数的类就是抽象类
    • C. 纯虚函数必须在派生类中实现才能创建派生类对象
    • D. 纯虚函数不能有函数体
    查看练习答案与解析

    答案:C 解析:抽象类不能创建对象(A 错),含虚函数不一定是抽象类(B 错),纯虚函数必须有派生类实现才能创建对象(C 对),纯虚函数可以有函数体(D 错)。

三、程序填空题(每空 2 分,共计 20 分)

  1. 实现下列求字符串长度的函数。

查看答案与解析

答案:(根据图片内容,典型字符串长度函数填空)

const char *strchar *str(函数参数声明) ② *str != '\0'*str(循环条件) ③ str++(指针移动) ④ countlen(返回值)

解析: 求字符串长度的核心思路是遍历字符串直到遇到结束符 '\0'

cpp
int strlen(const char *str) {
    int len = 0;
    while (*str != '\0') {
        len++;
        str++;
    }
    return len;
}

难度: ⭐⭐ 考点: #字符串 #指针遍历 #strlen

💡 学习锦囊

📖 相关公式与知识点:

  • C 风格字符串以 '\0' 结尾
  • 字符串长度不包含 '\0'
  • while (*str) 等价于 while (*str != '\0')

易错点

  • sizeof("hello") = 6(含 '\0'),strlen("hello") = 5
🔄 举一反三
  1. 编写函数实现字符串复制功能(类似 strcpy)
    查看练习答案与解析

    答案

    cpp
    void strcpy(char *dest, const char *src) {
        while (*src) *dest++ = *src++;
        *dest = '\0';
    }

    解析:逐字符复制,最后添加 '\0'

  1. 完成下列类的定义。

查看答案与解析

答案:(根据图片内容,典型类定义填空)

典型填空涉及:构造函数初始化列表、成员函数实现、this 指针使用、静态成员初始化等。

解析: 类定义填空题通常考查:

  1. 构造函数:使用初始化列表初始化成员
  2. this 指针:区分成员变量与参数同名
  3. 静态成员:类体外初始化
  4. 常成员函数:const 修饰
cpp
class MyClass {
    int x, y;
public:
    MyClass(int x, int y) : x(x), y(y) {}
    int getX() const { return x; }
    void setX(int x) { this->x = x; }
};

难度: ⭐⭐ 考点: #类定义 #构造函数 #this指针

💡 学习锦囊

📖 相关公式与知识点:

  • 初始化列表:类名(参数) : 成员1(值1), 成员2(值2) {}
  • this 指针:指向当前对象,区分同名变量
  • const 成员函数:不能修改数据成员

易错点

  • 初始化列表顺序应与成员声明顺序一致
  • const 成员函数不能修改非 mutable 数据成员
🔄 举一反三
  1. 下列构造函数初始化列表写法,正确的是( )
    • A. MyClass(int a) : x(a) {}
    • B. MyClass(int a) { x = a; }
    • C. MyClass(int a) : this->x(a) {}
    • D. MyClass(int a) : x = a {}
    查看练习答案与解析

    答案:A 解析:初始化列表使用 成员名(值) 格式。B 是函数体内赋值,C 不能用 this->,D 语法错误。

  1. 完成下列 point 类的定义,实现计算两点间欧氏距离的功能。
cpp
class point {
    int x, y;
public:
    point(int x = 0, int y = 0) {
        ① ;
        ② ;
    }
    double distance(point p) {
        return ③ ;
    }
};

int main() {
    point p1, p2(3, 4);
    double dist;
    ④ ;
    cout << dist << endl;
    return 0;
}
查看答案与解析

答案:this->x = xthis->y = ysqrt((double)((x - p.x) * (x - p.x) + (y - p.y) * (y - p.y)))dist = p1.distance(p2)

解析:

①②:构造函数中参数名与成员名相同,需用 this 指针区分。

:欧氏距离公式 $d = \sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2}$xy 是当前对象坐标,p.xp.y 是参数对象坐标。

:调用 p1 的 distance 成员函数,传入 p2 计算距离。

运行结果:5(p1(0,0) 到 p2(3,4) 的距离为 $\sqrt{9+16} = 5$


难度: ⭐⭐ 考点: #this指针 #成员函数 #欧氏距离

💡 学习锦囊

📖 相关公式与知识点:

  • 欧氏距离:$d = \sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2}$
  • this 指针:指向当前对象,区分同名变量
  • sqrt 函数需包含 <cmath>

易错点

  • 参数名与成员名相同时必须用 this 指针
  • 整数运算注意类型转换
🔄 举一反三
  1. 若 point 类坐标改为 double 类型,distance 函数应如何修改?
    查看练习答案与解析

    答案

    cpp
    double distance(point p) {
        return sqrt((x - p.x) * (x - p.x) + (y - p.y) * (y - p.y));
    }

    解析:坐标改为 double 后无需额外类型转换,运算自动按 double 进行。

四、阅读程序题(共计 30 分)

  1. 阅读该程序,给出程序的输出结果。(5 分)
cpp
#include <iostream>
void print(int);
void print(char);
void print(char *);

int main() {
    int a(800);
    print(a);
    print('a');
    print("break");
}

void print(int i) {
    cout << i << endl;
}

void print(char i) {
    cout << i << endl;
}

void print(char *i) {
    cout << i << endl;
}
查看答案与解析

答案:

800
a
break

解析: 本题考查函数重载的匹配规则。

  1. print(a):a 是 int 类型(值为 800),精确匹配 void print(int i),输出 800
  2. print('a')'a' 是 char 类型,精确匹配 void print(char i),输出 a
  3. print("break")"break" 是字符串常量(char* 类型),匹配 void print(char *i),输出 break

难度: ⭐ 考点: #函数重载 #参数匹配 #重载解析

💡 学习锦囊

📖 相关公式与知识点:

  • 函数重载:同名函数,参数列表不同
  • 匹配优先级:精确匹配 > 类型提升 > 标准转换 > 用户定义转换

易错点

  • print('a') 匹配 char 版本而非 int 版本(精确匹配优先)
  • 返回类型不同不能构成重载
🔄 举一反三
  1. 若增加 void print(double i) 函数,则 print(3.14f) 会调用哪个版本?
    查看练习答案与解析

    答案:调用 void print(double i)解析3.14f 是 float 类型,float 提升为 double,匹配 print(double) 版本。

  1. 阅读该程序,给出程序的输出结果。(5 分)
cpp
#include <iostream>
class Point {
public:
    Point(int i, int j) { X = i; Y = j; }
    Point(Point &rp);
    ~Point() { cout << "Destructor called.\n"; }
    int Xcoord() { return X; }
    int Ycoord() { return Y; }
private:
    int X, Y;
};

Point::Point(Point &rp) {
    X = rp.X;
    Y = rp.Y;
    cout << "Copy Constructor called.\n";
}

int main() {
    Point p1(6, 9);
    Point p2(p1);
    Point p3 = p2, p4(0, 0);
    p4 = p1;
    cout << "p3 = (" << p3.Xcoord() << "," << p3.Ycoord() << ")\n";
    cout << "p4 = (" << p4.Xcoord() << "," << p4.Ycoord() << ")\n";
}
查看答案与解析

答案:

Copy Constructor called.
Copy Constructor called.
p3 = (6,9)
p4 = (6,9)
Destructor called.
Destructor called.
Destructor called.
Destructor called.

解析:

  1. Point p1(6, 9):普通构造函数,无输出
  2. Point p2(p1):拷贝构造 → Copy Constructor called.
  3. Point p3 = p2:拷贝构造(初始化)→ Copy Constructor called.
  4. Point p4(0, 0):普通构造函数,无输出
  5. p4 = p1赋值操作,不调用拷贝构造函数
  6. 输出坐标
  7. 析构顺序:p4 → p3 → p2 → p1(后构造的先析构)

难度: ⭐⭐ 考点: #拷贝构造函数 #析构顺序 #初始化与赋值

💡 学习锦囊

📖 相关公式与知识点:

  • 拷贝构造调用时机:初始化新对象、值传递、返回对象
  • Point p3 = p2初始化,调用拷贝构造
  • p4 = p1赋值,不调用拷贝构造
  • 析构顺序与构造顺序相反

易错点

  • 初始化(= 声明)vs 赋值(= 操作)的区别
  • 析构顺序与构造顺序相反(栈的后进先出)
🔄 举一反三
  1. 若函数 void func(Point p) 被调用 func(p1),会输出什么?
    查看练习答案与解析

    答案Copy Constructor called.Destructor called.解析:值传递时实参初始化形参调用拷贝构造函数,函数结束时形参析构。

  1. 阅读该程序,给出程序的输出结果。(5 分)
cpp
#include <iostream>
class B {
public:
    B(int i, int j) { b1 = i; b2 = j; }
    void Print() { cout << b1 << "," << b2 << endl; }
private:
    int b1, b2;
};

class C {
public:
    C(int i) { c = i; }
    void Print() { cout << c << endl; }
private:
    int c;
};

class A {
public:
    A(int i, int j, int k, int l) : c(i), b(j, k) { a = l; }
    void Print() {
        b.Print();
        c.Print();
        cout << a << endl;
    }
private:
    B b;
    C c;
    int a;
};

int main() {
    B b(7, 8);
    b.Print();
    C c(9);
    c.Print();
    A a(4, 5, 6, 7);
    a.Print();
}
查看答案与解析

答案:

7,8
9
5,6
4
7

解析:

  1. B b(7, 8)b.Print() 输出 7,8
  2. C c(9)c.Print() 输出 9
  3. A a(4, 5, 6, 7):初始化列表 c(4), b(5, 6)a = 7
    • 成员 c:C(4),c.c = 4
    • 成员 b:B(5, 6),b.b1 = 5, b.b2 = 6
    • 成员 a = 7
  4. a.Print()b.Print()5,6c.Print()4cout << a7

难度: ⭐⭐ 考点: #类的组合 #成员初始化列表 #子对象构造

💡 学习锦囊

📖 相关公式与知识点:

  • 类的组合:一个类的对象作为另一个类的成员(子对象)
  • 构造顺序:按成员声明顺序构造,与初始化列表顺序无关

思路分析

分析 A(int i, int j, int k, int l) : c(i), b(j, k) { a = l; }

  • 参数 i → c 的构造函数 → c.c = 4
  • 参数 j, k → b 的构造函数 → b.b1=5, b.b2=6
  • 参数 l → a = 7

易错点

  • 初始化列表中参数的对应关系要仔细分析
  • 成员构造顺序取决于声明顺序,不是初始化列表顺序
🔄 举一反三
  1. 若将 A 的构造函数改为 A(int i, int j) : c(i), b(j, j) { a = i + j; },创建 A a(3, 5)a.Print() 输出什么?
    查看练习答案与解析

    答案

    5,5
    3
    8

    解析:c 初始化为 C(3),c.c=3;b 初始化为 B(5,5);a = 3+5 = 8。

  1. 阅读该程序,给出程序的输出结果。(5 分)
cpp
#include <iostream>
class A {
public:
    virtual void fun() { cout << "A::fun() called.\n"; }
};

class B : public A {
    void fun() { cout << "B::fun() called.\n"; }
};

void ffun(A *pa) {
    pa->fun();
}

int main() {
    A *pa = new A;
    ffun(pa);
    B *pb = new B;
    ffun(pb);
}
查看答案与解析

答案:

A::fun() called.
B::fun() called.

解析:

  1. ffun(pa):pa 指向 A 对象,pa->fun() 调用 A::fun()
  2. ffun(pb):pb 指向 B 对象,传入 ffun 时转为 A*,但实际指向 B 对象。fun() 是虚函数,动态联编调用 B::fun()

关键:虚函数通过基类指针调用时,根据指针实际指向的对象类型选择函数版本,实现运行时多态。


难度: ⭐⭐ 考点: #虚函数 #多态 #动态联编

💡 学习锦囊

📖 相关公式与知识点:

  • 动态联编条件:虚函数 + 基类指针/引用调用
  • 虚函数表(vtable):存储虚函数地址,运行时查找

易错点

  • 如果 fun() 不是虚函数,两次都调用 A::fun()
  • 只有通过指针/引用调用虚函数才是动态联编
🔄 举一反三
  1. 若将 virtual 关键字去掉,程序输出什么?
    查看练习答案与解析

    答案

    A::fun() called.
    A::fun() called.

    解析:去掉 virtual 后,调用根据指针的静态类型(A*)确定,两次都调用 A::fun()。

  1. 阅读该程序,给出程序的输出结果。(10 分)
cpp
#include <iostream>
class C {
public:
    C(int i) { c = i; }
    C() { c = 0; cout << "Default constructor called." << c << endl; }
    ~C() { cout << "Destructor called." << c << endl; }
    void Print() { cout << c << endl; }
private:
    int c;
};

int main() {
    C *p;
    p = new C[4];
    int n = 1;
    for (int i = 0; i < 4; i++)
        p[i] = C(n++);
    for (i = 0; i < 4; i++)
        p[i].Print();
    delete []p;
}
查看答案与解析

答案:

Default constructor called.0
Default constructor called.0
Default constructor called.0
Default constructor called.0
Destructor called.1
Destructor called.2
Destructor called.3
Destructor called.4
1
2
3
4
Destructor called.4
Destructor called.3
Destructor called.2
Destructor called.1

解析:

第一步:p = new C[4] 创建 4 个对象数组,每个调用默认构造函数 C(),输出 4 行 Default constructor called.0

第二步:p[i] = C(n++) 每次循环:C(n++) 创建临时对象(调用 C(int) 构造函数,无输出),赋值给 p[i],临时对象析构输出 Destructor called.X

  • i=0:C(1) → p[0].c=1,析构输出 Destructor called.1
  • i=1:C(2) → p[1].c=2,析构输出 Destructor called.2
  • i=2:C(3) → p[2].c=3,析构输出 Destructor called.3
  • i=3:C(4) → p[3].c=4,析构输出 Destructor called.4

第三步:p[i].Print() 输出 1 2 3 4

第四步:delete []p 按逆序析构:Destructor called.4321


难度: ⭐⭐⭐ 考点: #构造函数 #析构函数 #临时对象 #new与delete

💡 学习锦囊

📖 相关公式与知识点:

  • new 类名[n]:创建 n 个对象数组,每个调用默认构造函数
  • 临时对象:C(n++) 创建的临时对象在语句结束时析构
  • delete[]:按逆序析构数组中的对象

思路分析

分析此类程序的关键步骤:

  1. 识别每次对象创建调用的构造函数
  2. 注意临时对象的创建与销毁时机
  3. 追踪每个对象的成员变量值
  4. 注意析构顺序(与构造顺序相反)

易错点

  • p[i] = C(n++) 是赋值操作,不是拷贝构造
  • 临时对象 C(n++) 在赋值完成后立即析构
  • delete[] 的析构顺序是逆序的
  • 默认构造函数有输出,而 C(int) 构造函数没有输出
🔄 举一反三
  1. 若将 C(int i) { c = i; } 改为 C(int i) { c = i; cout << "Parameter constructor called." << c << endl; },程序输出会有什么变化?
    查看练习答案与解析

    答案:在每次创建临时对象时多输出一行 Parameter constructor called.X解析:每个临时对象创建时都会调用 C(int) 构造函数并输出,析构时机不变。

  2. 若将 p[i] = C(n++) 改为 p[i] = C(n)(去掉 n++),程序输出有什么变化?
    查看练习答案与解析

    答案:所有临时对象都是 C(1),析构输出 4 次 Destructor called.1,Print 输出 4 个 1,delete[] 输出 4 次 Destructor called.1解析:n 始终为 1,所有 p[i].c 都为 1。

五、编程题(每题 10 分,共计 20 分)

  1. 按下列要求编程:

定义一个描述日期的类 Date,包括的数据成员有年(year)、月(month)和日(day),并实现如下功能函数:

(1)日期对象初始化; (2)以 year/month/day 形式输出日期; (3)判断闰年; (4)改变日期。

查看答案与解析

答案:

cpp
#include <iostream>
using namespace std;

class Date {
private:
    int year, month, day;

public:
    Date(int y = 2000, int m = 1, int d = 1) : year(y), month(m), day(d) {}

    void Print() {
        cout << year << "/" << month << "/" << day << endl;
    }

    bool IsLeapYear() {
        return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
    }

    void SetDate(int y, int m, int d) {
        year = y;
        month = m;
        day = d;
    }
};

int main() {
    Date d(2024, 2, 29);
    d.Print();
    cout << "Is leap year: " << (d.IsLeapYear() ? "Yes" : "No") << endl;
    d.SetDate(2023, 3, 15);
    d.Print();
    return 0;
}

解析:

(1)日期对象初始化:使用带默认参数的构造函数 Date(int y = 2000, int m = 1, int d = 1),支持无参和有参两种初始化方式。

(2)输出日期Print() 函数以 year/month/day 格式输出。

(3)判断闰年IsLeapYear() 函数,闰年规则:

  • 能被 4 整除且不能被 100 整除,能被 400 整除
  • 公式:(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)

(4)改变日期SetDate() 函数修改年、月、日。


难度: ⭐⭐ 考点: #类定义 #构造函数 #成员函数 #闰年判断

💡 学习锦囊

📖 相关公式与知识点:

  • 闰年判断规则:
    • 普通闰年:能被 4 整除但不能被 100 整除(如 2024 年)
    • 世纪闰年:能被 400 整除(如 2000 年)
    • 非闰年:能被 100 整除但不能被 400 整除(如 1900 年)
  • 构造函数初始化列表:Date(int y, int m, int d) : year(y), month(m), day(d) {}
  • 带默认参数的构造函数可兼作默认构造函数

思路分析

设计日期类的步骤:

  1. 确定数据成员:year、month、day
  2. 设计构造函数:带默认参数,方便不同方式创建对象
  3. 实现各功能函数:Print、IsLeapYear、SetDate
  4. 注意闰年的判断逻辑要完整

易错点

  • 闰年判断不能只看能否被 4 整除,还需排除能被 100 整除但不能被 400 整除的年份
  • 1900 年不是闰年(能被 100 整除但不能被 400 整除)
  • 2000 年是闰年(能被 400 整除)
🔄 举一反三
  1. 在 Date 类中增加一个函数 int DaysOfMonth(),返回当前月份的天数(考虑闰年)。
    查看练习答案与解析

    答案

    cpp
    int DaysOfMonth() {
        int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
        if (month == 2 && IsLeapYear()) return 29;
        return days[month];
    }

    解析:使用数组存储每月天数,2 月需特殊处理闰年情况。

  1. 根据下列 Vector 类定义,编程完成 Vector 类的具体实现:
cpp
#include <iostream>
class Vector {
    friend ostream& operator << (ostream& out, const Vector& v);
private:
    int* data;
    int size;
public:
    Vector(int v[]);
    ~Vector();
    Vector(const Vector &v);
    Vector& operator=(const Vector &v);
    int& operator[](int index);
};
查看答案与解析

答案:

cpp
#include <iostream>
using namespace std;

class Vector {
    friend ostream& operator << (ostream& out, const Vector& v);
private:
    int* data;
    int size;
public:
    Vector(int v[]) : size(5) {
        data = new int[size];
        for (int i = 0; i < size; i++)
            data[i] = v[i];
    }

    ~Vector() {
        delete[] data;
    }

    Vector(const Vector &v) : size(v.size) {
        data = new int[size];
        for (int i = 0; i < size; i++)
            data[i] = v.data[i];
    }

    Vector& operator=(const Vector &v) {
        if (this != &v) {
            delete[] data;
            size = v.size;
            data = new int[size];
            for (int i = 0; i < size; i++)
                data[i] = v.data[i];
        }
        return *this;
    }

    int& operator[](int index) {
        return data[index];
    }
};

ostream& operator << (ostream& out, const Vector& v) {
    for (int i = 0; i < v.size; i++)
        out << v.data[i] << ";";
    return out;
}

解析:

(1)构造函数 Vector(int v[]):从数组初始化,动态分配内存并复制数据。

(2)析构函数 ~Vector():释放动态分配的内存 delete[] data

(3)拷贝构造函数 Vector(const Vector &v):深拷贝,重新分配内存并复制数据。必须实现深拷贝,否则两个对象的 data 指向同一内存,析构时会重复释放。

(4)赋值运算符 operator=

  • 自赋值检查:if (this != &v)
  • 释放原有内存:delete[] data
  • 重新分配并复制
  • 返回 *this 支持链式赋值

(5)下标运算符 operator[]:返回引用,支持读写操作。

(6)输出运算符 operator<<:友元函数,访问私有成员,输出向量元素。


难度: ⭐⭐⭐ 考点: #动态内存管理 #深拷贝 #运算符重载 #拷贝构造函数

💡 学习锦囊

📖 相关公式与知识点:

  • 深拷贝 vs 浅拷贝:含动态内存的类必须实现深拷贝
  • 拷贝构造函数:创建新对象时调用
  • 赋值运算符:已存在对象赋值时调用
  • 自赋值检查:if (this != &v) 防止自赋值导致错误
  • 三法则(Rule of Three):如果需要自定义析构函数、拷贝构造函数或赋值运算符中的任何一个,通常需要自定义全部三个

思路分析

实现 Vector 类的关键步骤:

  1. 构造函数:动态分配内存,复制数据
  2. 析构函数:释放动态内存
  3. 拷贝构造:深拷贝,重新分配内存
  4. 赋值运算:先释放旧内存,再深拷贝,注意自赋值检查
  5. 下标运算:返回引用支持读写
  6. 输出运算:友元函数访问私有成员

易错点

  • 赋值运算符必须检查自赋值,否则 delete[] data 后再访问 v.data 会出错
  • 拷贝构造函数必须深拷贝,浅拷贝会导致析构时重复释放内存
  • 赋值运算符中必须先释放旧内存再分配新内存
  • operator[] 返回引用才能支持 v[i] = 10 这样的写操作
🔄 举一反三
  1. 若在 Vector 类中增加一个默认构造函数 Vector() : data(NULL), size(0) {},赋值运算符需要修改吗?
    查看练习答案与解析

    答案:不需要修改。当前赋值运算符的实现已经正确处理了 delete[] data(对 NULL 指针 delete[] 是安全的)和重新分配内存的逻辑。 解析:C++ 标准规定 delete[] NULL 是安全的,不会产生错误。

  2. 若将拷贝构造函数改为浅拷贝(data = v.data; size = v.size;),程序会出现什么问题?
    查看练习答案与解析

    答案:会导致两个对象的 data 指向同一块内存,当两个对象析构时,同一块内存会被 delete[] 两次,导致未定义行为(double free)。 解析:这就是为什么含动态内存的类必须实现深拷贝。

你正在阅读的是会员专属文档,💕 限时特惠进行中
你尚未登录,目前新用户可获3天体验会员,去登录